/** Base Class for stimuli that draw a world composed of dots,
    e.g. a uniform density dot ground.

    DCN 27-06-2010
*/

#pragma once
#include "StimBase.h"
#include "StimObj.h"

#ifdef _DEBUG 
    #define STIM_DOT_SAVE_DOTS 0        // if 1 and in DEBUG configuration, save dots of each frame drawn to file (no protection against duplicates, every frame drawn is saved)
#endif

class CStimDot :
    public CStimBase, public CStimObj
{
public:
    CStimDot(const enum Stims::StimType& eStimType_, const enum Stims::ObjType& eObjType_);
    virtual ~CStimDot(void);

    // prepare stimulus
    virtual void Reset          ();

    // change stimulus
    virtual void Update         (const CMatrix<double>& mEyePos_, const double& dYaw_);
    virtual void Draw           ();

    // other
    virtual std::string GetName () = 0;

    //// setters
    // stimulus category setters (e.g. lifetime)
    virtual int&                SetDotLifeTime()        {return _nDotLifeTime;}     // For limited lifetime stimuli, lifetime in number of frames, not yet implemented
    virtual int&                SetFormOffset2DOr3D()   {return _nFormOffset;}      // create dot-pair stimulus, using 2D offsets (set to 2) or 3D offsets, not yet implemented
    // algorithm parameters
    virtual double&             SetFOVy()               {return _dFOVy;}            // set field of view in vertical direction
    virtual double&             SetAspectRatio()        {return _dAspectRatio;}     // set aspect ratio
    virtual double&             SetNearPlane()          {return _dNearPlane;}       // set the near clipping plane
    virtual double&             SetFarPlane()           {return _dFarPlane;}        // set the far clipping plane
    virtual double&             SetEyeHeight()          {return _dEyeHeight;}       // set eye height

    virtual int&                SetNArea()              {return _nNArea;}            // set number of slices, e.g. 50 is okay for 300 dots
    virtual int&                SetNPoint()             {return _nNPoint;}           // set number of dots, sphere, or discs.

    // form offset parameters
    virtual double&             SetFormOffsetAngle()    {return _dFormOffsetAngle;}     // Angle between form center and motion center
    virtual double&             SetFormOffset()         {return _dFormOffset;}          // Offset between dots of each dot pair, in visual angle offset for 2D Form stimuli, in meters for 3D stimuli


    // specific for this stimulus
protected:
    // functions
    virtual void    InitVectors(); 
    virtual void    ComputeDistribution(); 
    virtual double  ComputeSlice(const double& dDepth_,const int& nIdx_) = 0; 
    void            PerFramePrecompute(const CMatrix<double>& mEyePos_, const double& dYaw_); 
    void            UpdateAllPointLists(); 
    void            DrawToDisplayList();  

    // helpers
    void            UpdatePointList(const double& dRadiusX_,const double& dRadiusY_,const double& dRadiusZ_,const int& nNumWeWant_, unsigned int& uIdx_);
    void            GenerateRandomFrustum(const double& dRadiusX_,const double& dRadiusY_,const double& dRadiusZ_, const int& nNumWeWant_, unsigned int& uIdx_, const bool& bInsideOld_);
    virtual void    SamplePoint(const double& dRadiusZ_, CMatrix<double>& mPoint_) = 0;
    bool            InsideFrustum(const CMatrix<double>& mPoint_, const double& dRadiusX_,const double& dRadiusY_,const double& dRadiusZ_,const CMatrixR& mRotMat_,const CMatrix<double>& mEyePos_);
    virtual bool    InsideFrustumDerived(const CMatrix<double>& mOriginPoint_) = 0;     // called inside inside frustum, for some stimuli extra checks are needed.

#if STIM_DOT_SAVE_DOTS
    void SaveToFile();
    int _nCurrentFrame;
    // this below is all to be able to track dot identity over time, each dot is assigned a unique ID
    std::vector<std::vector<int>>       _vvPointIDs;   // vector of areas with for each area a list with for each point an unique ID
    int _nNextPointID;
#endif

    //// input:
    // limited lifetime
    int                                 _nDotLifeTime;
    // form stimuli (Glass pattern clouds)
    int                                 _nFormOffset;
    double                              _dFormOffsetAngle;
    double                              _dFormOffset;

    // frustum and observer specification
    double                              _dFOVy;
    double                              _dAspectRatio;
    double                              _dNearPlane;
    double                              _dFarPlane;
    double                              _dEyeHeight;

    // algorithm specification
    int                                 _nNArea;
    int                                 _nNPoint;

    // display list
    GLuint                              _uPointList;

    // per frame: eye position, viewing direction
    CMatrix<double>                     _mEyePosN;      // eye position current frame
    CMatrix<double>                     _mEyePosO;      // eye position previous frame

    //// variables:
    // computed once
    double                              _dTanFOVy_2;    // tangent of half the FOVy
    double                              _dTanFOVx_2;    // tangent of half the FOVx

    std::vector<int>                    _vNumDot;       // number of dots inside each area

    std::vector<double>                 _vHalfLengthX;  // sizes of the areas
    std::vector<double>                 _vHalfLengthY;
    std::vector<double>                 _vHalfLengthZ;

    // computed per frame
    // for all
    std::vector<std::vector<double>>    _vvPointList;   // vector of areas with for each area a list of points inside it, defined as <x y z, x y z, etc>
    CMatrixR                            _mRotationNG;   // rotation matrix current  frame, generating points and rotating to position in the world
    CMatrixR                            _mRotationNT;   // rotation matrix current  frame, rotating points back to origin and testing if inside frustum
    CMatrixR                            _mRotationOT;   // rotation matrix previous frame, rotating points back to origin and testing if inside frustum
    // limited lifetime
    std::vector<std::vector<int>>       _vvPointLifeList;   // vector of areas with for each area a list of point lifetimes inside it, in number of frames left to live
    // other
    bool                                _bFirstFrame;   // is this the first frame that is generated?
};